package ms.jasim.pddl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import ms.jasim.framework.IConfiguration;
import ms.jasim.framework.JasimFramework;
import ms.jasim.model.Actor;
import ms.jasim.model.Goal;
import ms.utils.INamedItem;
import ms.utils.NamedList;

public class PddlSolution implements Serializable, Iterable<PddlSolution.PddlAction>, INamedItem {
	private static final long serialVersionUID = 1L;

	public static class PddlActor implements INamedItem  {
		public String Name;
		public final List<PddlGoal> goals = new ArrayList<PddlGoal>();
		public final List<PddlAction> actions = new ArrayList<PddlAction>();

		public String getName() {
			return Name;
		}
	}

	public static class PddlGoal implements INamedItem  {
		public String Name;

		public String getName() {
			return Name;
		}
	}

	public enum Functor {
		PASSES, SATISFIES, COMBINESAND2, COMBINESAND3, COMBINESOR2, COMBINESOR3, AND_DECOMPOSES2, AND_DECOMPOSES3, OR_DECOMPOSES2, OR_DECOMPOSES3
	};

	public class PddlAction implements Serializable {
		private static final long serialVersionUID = 1L;

		private String name;
		private Functor functor;
		private List<String> argument = new ArrayList<String>();
		
		public final int index;

		protected Actor actor;

		protected Goal goal;

		public PddlAction(int idx, String command) {
			index = idx;
			String[] s = command.split(" ");
			name = s[0];
			functor = Functor.valueOf(name);
			for (int i = 1; i < s.length; i++)
				argument.add(s[i]);
		}

		public String Name() {
			return name;
		}
 		
		public Functor getFunctor() {
			return functor;
		}
		
		public int ArgumentCount() {
			return argument.size();
		}

		public String getArgument(int index) {
			return argument.get(index);
		}
		
		public List<String> getArguments() {
			return argument;
		}

		public void toString(StringBuilder res) {
			if (JasimFramework.getConfiguration().getBoolean(IConfiguration.DISPLAY_OBJ_TEXT, false)) {
				switch (this.getFunctor()) {
				case PASSES:
					Actor target = problem.getModel().getActors().get(getArgument(1));
					
					res.append(getActor().getText());
					res.append(" delegates ");
					res.append(getGoal().getText());
					res.append(" to ");
					res.append( target != null ? target.getText() : getArgument(1));
					break;
				case SATISFIES:
					res.append(getActor().getText());
					res.append(" satisfies ");
					res.append(getGoal().getText());
					break;
				case AND_DECOMPOSES2:
				case AND_DECOMPOSES3:
				case OR_DECOMPOSES2:
				case OR_DECOMPOSES3:
					res.append(getActor().getText());
					res.append(" decomposes (" + (getFunctor().toString().startsWith("AND") ? "AND" : "OR") + ") ");
					res.append(getGoal().getText());
					res.append(" into ");
					for (int i = 2, n = getArguments().size(); i < n; i ++) {
						Goal g = problem.getModel().getGoals().get(getArgument(i));
						res.append(g != null ? g.getText() : getArgument(i));
						if (i < n - 1)
							res.append(", ");
					}
					break;
				}
			}
			else {
				res.append(name + "(");
				for (int i = 0; i < argument.size(); i++) {
					if (i > 0)
						res.append(',');
					res.append(argument.get(i));
				}
				res.append(")");
			}
		}

		@Override
		public String toString() {
			StringBuilder s = new StringBuilder();
			toString(s);
			return s.toString();
		}

		public Actor getActor() {
			if (actor == null) 
				this.actor = problem.getModel().getActors().get(getArgument(0));
			return actor;
		}
		
		public Goal getGoal() {
			if (goal == null) 
				this.goal = problem.getModel().getGoals()
					.get(getArgument(this.functor == Functor.PASSES ? 2 : 1));
			return goal;
		}
	
		
	}
	
	public class SatisfiesAction extends PddlAction {

		private static final long serialVersionUID = 1L;

		public SatisfiesAction(int idx, String command) {
			super(idx, command);
		}
		
		@Override
		public void toString(StringBuilder res) {
			res.append(getActor().getText());
			res.append(" satisfies ");
			res.append(getGoal().getText());
		}
	}
	
	public class PassesAction extends PddlAction {

		private static final long serialVersionUID = 1L;

		public PassesAction(int idx, String command) {
			super(idx, command);
		}

		@Override
		public void toString(StringBuilder res) {
			Actor target = problem.getModel().getActors().get(getArgument(1));
			
			res.append(getActor().getText());
			res.append(" delegates ");
			res.append(getGoal().getText());
			res.append(" to ");
			res.append( target != null ? target.getText() : getArgument(1));
		}
	}
	
	public class Decompose extends PddlAction {

		private static final long serialVersionUID = 1L;

		public Decompose(int idx, String command) {
			super(idx, command);
		}
		
		@Override
		public void toString(StringBuilder res) {
			res.append(getActor().getText());
			res.append(" decomposes (" + (getName().startsWith("AND") ? "AND" : "OR") + ") into " );
			for (int i = 2, n = getArguments().size(); i < n; i ++) {
				Goal g = problem.getModel().getGoals().get(getArgument(i));
				res.append(g != null ? g.getText() : getArgument(i));
				if (i < n - 1)
					res.append(", ");
			}
		}
	}
	
	private List<PddlAction> actions = new ArrayList<PddlAction>();

	private NamedList<PddlActor> actors;
	private NamedList<PddlGoal> goals;
	private String name;

	private PddlProblem problem;

	public PddlSolution(PddlProblem problem) {
		this.setProblem(problem);
	}

	public PddlSolution(PddlProblem problem, String solutionFile) throws FileNotFoundException {
		this(problem);
		if (solutionFile != null)
			load(solutionFile);
	}

	public void load(String filename) throws FileNotFoundException {
		actions.clear();
		actors = new NamedList<PddlActor>();
		goals = null;

		Map<String, PddlActor> actor_map = new HashMap<String, PddlActor>();

		Scanner scanner = new Scanner(new File(filename));
		while (scanner.hasNextLine()) {
			String line = scanner.nextLine();
			if (!line.startsWith(";")) {
				int sp = line.indexOf('(');
				int ep = line.indexOf(')');
				if (sp >= 0 && ep > 0) {
					String command = line.substring(sp + 1, ep);
					if (!command.startsWith("COMBINE")) {
						PddlAction act = new PddlAction(actions.size(), command);					
						PddlActor a = actor_map.get(act.getArgument(0));
						if (a == null) {
							a = new PddlActor();
							a.Name = act.getArgument(0);
							actor_map.put(a.Name, a);
							actors.add(a);
						}
						a.actions.add(act);						
						actions.add(act);
					}
				}
			}
		}
		scanner.close();
	}

	public String getName() {
		return name;
	}
	
	public void setName(String value) {
		name = value;
	}
	
	public NamedList<PddlActor> Actors() throws Exception {
		if (actors == null)
			populateSolutionInfomation();
		return actors;
	}

	public NamedList<PddlGoal> Goals() throws Exception {
		if (goals == null)
			populateSolutionInfomation();
		return goals;
	}

	public List<PddlAction> Actions() {
		return actions;
	}
	
	protected void populateSolutionInfomation() throws Exception {
		actors = new NamedList<PddlActor>();
		goals = new NamedList<PddlGoal>();

		for (PddlAction action: actions) {
			if ("SATISFIES".equalsIgnoreCase(action.Name())) {
				PddlActor a = actors.get(action.getArgument(0));
				if (a == null) {
					a = new PddlActor();
					a.Name = action.getArgument(0);
					actors.add(a);
				}
				PddlGoal g = new PddlGoal();
				g.Name = action.getArgument(1);
				a.goals.add(g);
				goals.add(g);
			}
		}
	}

	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append("Solution: " + getName() + "\r\n");
		res.append("Number of action: " + actions.size() + "\r\n");
		res.append("-------------\r\n");
		for (PddlAction a : actions) {
			res.append(a.index + ": ");
			a.toString(res);
			res.append("\r\n");
		}
		return res.toString();
	}

	public List<PddlAction> toArray() {
		return new ArrayList<PddlAction>(actions);
	}
	
	@Override
	public Iterator<PddlAction> iterator() {
		return actions.iterator();
	}

	
	public void setProblem(PddlProblem problem) {
		this.problem = problem;
	}

	
	public PddlProblem getProblem() {
		return problem;
	}
}
